// ============================================================================
// FileName: DatabaseLogger.cs
//
// Description:
// Logs events from the StatefulProxy to a database.
//
// Author(s):
// Aaron Clauson
//
// History:
// 12 Nov 2006	Aaron Clauson	Created.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
// ============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using BlueFace.Sys;
using BlueFace.VoIP.Net.SIP;
using BlueFace.VoIP.SIPServer;
using log4net;

namespace BlueFace.VoIP.SIP.StatefulProxy
{
    public class DatabaseLogger
    {
        private const int PROXYEVENT_QUEUE_MAXSIZE = 100;
        private const int CHECK_PERIOD = 10000;
        
        private static ILog logger = log4net.LogManager.GetLogger("sipproxy");

        private StorageLayer m_logStorageLayer = null;

        private static Queue<ProxyMonitorEvent> m_proxyLogEventQueue = new Queue<ProxyMonitorEvent>();  // Queue for events to be written to database.
        private static Queue<SIPCDR> m_proxyCDRQueue = new Queue<SIPCDR>();                             // Queue for CDRs to be written to the database.
        private static ManualResetEvent m_proxyLogEventWaiting = new ManualResetEvent(false);           // Fired to indicate there is a new log event in the queue.
        private static ManualResetEvent m_cdrEventWaiting = new ManualResetEvent(false);                // Fired to indicate there is a new CDR in the queue.

        private bool m_logEvents = true;   // Set to false to stop logging events to database.

        public DatabaseLogger(StorageTypes storageType, string dbConnStr)
        {
            try
            {
                logger.Debug("DatabaseLogger started.");

                m_logStorageLayer = new StorageLayer(storageType, dbConnStr);

                Thread dbLoggerThread = new Thread(new ThreadStart(ProcessLogEventQueue));
                dbLoggerThread.Name = "dblogger-thread";
                dbLoggerThread.Start();

                Thread cdrLoggerThread = new Thread(new ThreadStart(ProcessCDRQueue));
                cdrLoggerThread.Name = "cdrogger-thread";
                cdrLoggerThread.Start();
            }
            catch (Exception excp)
            {
                logger.Error("Exception DatabaseLogger. " + excp.Message);
            }
        }

        /// <summary>
        /// Used to allow the owning application to add a new event into the queue to be written to the database.
        /// </summary>
        public void AddEvent(ProxyMonitorEvent proxyEvent)
        {
            try
            {
                lock (m_proxyLogEventQueue)
                {
                    if (m_proxyLogEventQueue.Count > PROXYEVENT_QUEUE_MAXSIZE)
                    {
                        logger.Error("The proxy log queue has exceeded the threshold " + PROXYEVENT_QUEUE_MAXSIZE + " and is being dumped.");
                        m_proxyLogEventQueue.Clear();
                    }

                    m_proxyLogEventQueue.Enqueue(proxyEvent);
                    m_proxyLogEventWaiting.Set();
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception AddEvent. " + excp.Message);
            }
        }

        public void AddCDR(SIPCDR cdr)
        {
            try
            {
                lock (m_proxyCDRQueue)
                {
                    if (m_proxyCDRQueue.Count > PROXYEVENT_QUEUE_MAXSIZE)
                    {
                        logger.Error("The cdr log queue has exceeded the threshold " + PROXYEVENT_QUEUE_MAXSIZE + " and is being dumped.");
                        m_proxyCDRQueue.Clear();
                    }

                    m_proxyCDRQueue.Enqueue(cdr);
                    m_cdrEventWaiting.Set();
                }
            }
            catch (Exception excp)
            {
                logger.Equals("Exception AddCDR. " + excp.Message);
            }
        }
                
        private void ProcessLogEventQueue()
        {
            try
            {
                while (m_logEvents)
                {
                    try
                    {
                        while (m_proxyLogEventQueue.Count > 0)
                        {
                            ProxyMonitorEvent proxyLogEvent = null;

                            lock (m_proxyLogEventQueue)
                            {
                                proxyLogEvent = m_proxyLogEventQueue.Dequeue();
                            }

                            if (proxyLogEvent != null && proxyLogEvent.Username != null && proxyLogEvent.Message != null)
                            {
                                string insertSQL = "insert into switchlog (logid, username, logmessage, inserted) values " +
                                    "(default,'" + proxyLogEvent.Username + "','" + proxyLogEvent.Message.Replace("'", "''") + "', default);";

                                m_logStorageLayer.ExecuteNonQuery(insertSQL);
                            }
                        }
                    }
                    catch (Exception dbLogExcp)
                    {
                        logger.Error("Exception ProcessingLogEvent. " + dbLogExcp);
                    }

                    m_proxyLogEventWaiting.Reset();
                    m_proxyLogEventWaiting.WaitOne(CHECK_PERIOD, false);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception ProcessLogEventQueue, thread stopped. " + excp.Message);
            }
        }

        private void ProcessCDRQueue()
        {
            try
            {
                while (m_logEvents)
                {
                    string debugSQL = null;

                    try
                    {
                        while (m_proxyCDRQueue.Count > 0)
                        {
                            SIPCDR cdr = null;

                            lock (m_proxyCDRQueue)
                            {
                                cdr = m_proxyCDRQueue.Dequeue();
                            }

                            if (cdr != null)
                            {
                                bool exists = Convert.ToInt32(m_logStorageLayer.ExecuteScalar("select count(*) from cdr where cdrid = '" + cdr.CDRId + "'")) > 0;

                                string ownerStr = (cdr.Owner != null) ? "'" + cdr.Owner.Replace("'", "''") + "'" : "null";
                                string dst = (cdr.Destination != null) ? cdr.Destination.User.Replace("'", "''") : "null";
                                string dstHost = cdr.Destination.Host.Replace("'", "''");
                                string dstURI = cdr.Destination.ToString().Replace("'", "''");
                                string fromUser = (cdr.From.FromURI.User != null) ? "'" + cdr.From.FromURI.User.Replace("'", "''") + "'" : "null";
                                string fromName = (cdr.From.FromName != null) ? "'" + cdr.From.FromName.Replace("'", "''") + "'" : "null";
                                string from = cdr.From.ToString().Replace("'", "''");
                                string callId = cdr.CallId.Replace("'", "''");
                                string bridgeId = (cdr.BridgeId != Guid.Empty) ? "'" + cdr.BridgeId + "'" : "null";
                                string progressTime = (cdr.InProgress) ? "'" + cdr.ProgressTime.ToString("dd MMMM yyyy HH:mm:ss") + "'" : "null";
                                string progressReason = (cdr.ProgressReasonPhrase != null) ? "'" + cdr.ProgressReasonPhrase.Replace("'", "''") + "'" : "null";
                                double progressDuration = cdr.GetProgressDuration();
                                string answeredTime = (cdr.IsAnswered) ? "'" + cdr.AnswerTime.ToString("dd MMMM yyyy HH:mm:ss") + "'" : "null";
                                string answeredReason = (cdr.AnswerReasonPhrase != null) ? "'" + cdr.AnswerReasonPhrase.Replace("'", "''") + "'" : "null";
                                double answeredDuration = cdr.GetAnsweredDuration();
                                string hungupTime = (cdr.IsHungup) ? "'" + cdr.HangupTime.ToString("dd MMMM yyyy HH:mm:ss") + "'" : "null";
                                string hungupReason = (cdr.HangupReason != null) ? "'" + cdr.HangupReason.Replace("'", "''") + "'" : "null";

                                if (!exists)
                                {
                                    string insertSQL =
                                        "insert into cdr(" +
                                        "cdrid," +
                                        "owner," +
                                        "direction," +
                                        "created," +
                                        "dst," +
                                        "dsthost," +
                                        "dsturi," +
                                        "fromuser," +
                                        "fromname," +
                                        "\"from\"," +
                                        "callid," +
                                        "localsocket," +
                                        "remotesocket," +
                                        "bridgeid," +
                                        "inprogresstime," +
                                        "inprogressstatus," +
                                        "inprogressreason," +
                                        "ringingduration," +
                                        "answeredtime," +
                                        "answeredstatus," +
                                        "answeredreason," +
                                        "duration," +
                                        "hunguptime, " +
                                        "hungupreason) values (" +
                                        "'" + cdr.CDRId + "', " +
                                        ownerStr + ", " +
                                        "'" + cdr.CallDirection + "'," +
                                        "'" + cdr.Created.ToString("dd MMM yyyy HH:mm:ss") + "'," +
                                        "'" + dst + "'," +
                                        "'" + dstHost + "'," +
                                        "'" + dstURI + "'," +
                                        fromUser + "," +
                                        fromName + "," +
                                        "'" + from + "'," +
                                        "'" + callId + "'," +
                                        "'" + cdr.LocalEndPoint + "'," +
                                        "'" + cdr.RemoteEndPoint + "'," +
                                        bridgeId + "," +
                                        progressTime + "," +
                                        cdr.ProgressStatus + "," +
                                        progressReason + "," +
                                        progressDuration.ToString("0") + "," +
                                        answeredTime + "," +
                                        cdr.AnswerStatus + "," +
                                        answeredReason + "," +
                                        answeredDuration.ToString("0") + "," +
                                        hungupTime + "," +
                                        hungupReason + ")";

                                    //logger.Debug(insertSQL); 
                                    debugSQL = insertSQL;
                                    m_logStorageLayer.ExecuteNonQuery(insertSQL);
                                }
                                else
                                {
                                    string updateSQL =
                                        "update cdr set " +
                                        "bridgeid=" + bridgeId + "," +
                                        "inprogresstime=" + progressTime + "," +
                                        "inprogressstatus=" + cdr.ProgressStatus + "," +
                                        "inprogressreason=" + progressReason + "," +
                                        "ringingduration=" + progressDuration.ToString("0") + "," +
                                        "answeredtime=" + answeredTime + "," +
                                        "answeredstatus=" + cdr.AnswerStatus + "," +
                                        "answeredreason=" + answeredReason + "," +
                                        "duration=" + answeredDuration.ToString("0") + "," +
                                        "hunguptime=" + hungupTime + "," +
                                        "hungupreason=" + hungupReason + " " +
                                        "where cdrid='" + cdr.CDRId + "'";

                                    //logger.Debug(updateSQL);
                                    debugSQL = updateSQL;
                                    m_logStorageLayer.ExecuteNonQuery(updateSQL);
                                }
                            }
                        }
                    }
                    catch (Exception insertCDRExcp)
                    {
                        logger.Error("Exception DatabaseLogger Inserting CDR. " + insertCDRExcp);
                        logger.Error(debugSQL);
                    }

                    m_cdrEventWaiting.Reset();
                    m_cdrEventWaiting.WaitOne(CHECK_PERIOD, false);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception ProcessCDRQueue, thread stopped. " + excp.Message);
            }
        }

        public void Stop()
        {
            try
            {
                m_logEvents = false;
                m_proxyLogEventWaiting.Set();
                m_cdrEventWaiting.Set();
            }
            catch (Exception excp)
            {
                logger.Error("Exception DatabaseLogger Stop. " + excp.Message);
            }
        }
    }
}
